package com.yami.shop.nftaccount.common.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yami.shop.bean.enums.TradeType;
import com.yami.shop.bean.event.AssetTrendsEvent;
import com.yami.shop.bean.model.Blockchain;
import com.yami.shop.bean.model.ChannelUserRelationRecord;
import com.yami.shop.bean.model.Product;
import com.yami.shop.bean.vo.AssetTrendsVO;
import com.yami.shop.common.config.Constant;
import com.yami.shop.common.exception.YamiShopBindException;
import com.yami.shop.common.util.DataWebService;
import com.yami.shop.common.util.RedisUtil;
import com.yami.shop.common.util.StringUtils;
import com.yami.shop.config.ShopConfig;
import com.yami.shop.consign.common.service.ConsignSituationService;
import com.yami.shop.nftaccount.common.dao.AssetExceptionMapper;
import com.yami.shop.nftaccount.common.dao.NftAssetSettleMapper;
import com.yami.shop.nftaccount.common.dao.NftAssetTrendsMapper;
import com.yami.shop.nftaccount.common.model.AssetException;
import com.yami.shop.nftaccount.common.model.NftAssetSettle;
import com.yami.shop.nftaccount.common.model.NftAssetTrends;
import com.yami.shop.nftaccount.common.service.NftAssetSettleService;
import com.yami.shop.nftaccount.common.service.NftAssetTrendsService;
import com.yami.shop.personnft.common.service.PersonNftService;
import com.yami.shop.service.BlockchainService;
import com.yami.shop.service.ChannelUserRelationRecordService;
import com.yami.shop.service.ProductService;
import com.yami.shop.service.SysConfigService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

@Slf4j
@Service
@AllArgsConstructor
public class NftAssetTrendsServiceImpl extends ServiceImpl<NftAssetTrendsMapper, NftAssetTrends> implements NftAssetTrendsService {

    private final NftAssetTrendsMapper assetTrendsMapper;

    private final NftAssetSettleMapper assetSettleMapper;

    private final AssetExceptionMapper assetExceptionMapper;

    @Autowired
    private NftAssetSettleService assetSettleService;

    @Autowired
    private ShopConfig shopConfig;

    @Autowired
    private ProductService productService;

    @Autowired
    private BlockchainService blockchainService;

    @Autowired
    private ChannelUserRelationRecordService chainUserService;

    @Autowired
    private PersonNftService personNftService;

    @Autowired
    private ConsignSituationService consignService;

    @Autowired
    private SysConfigService sysConfigService;
    @Autowired
    private DataWebService dataWebService;
    private static final int QUERY_PAGE_SIZE = 200;

    private final String CHECK_TRENDS_KEY = "mallcloud_nftaccount:check_trends";

    private final String UNDERLINE = "_";

    @Override
    public void insert(NftAssetTrends trends) {
        saveTrends(Collections.singletonList(trends));
    }

    @Override
    public void insertBatch(List<NftAssetTrends> trendsList) {
        this.saveTrends(trendsList);
    }

    @Override
    @Transactional
    public void assemblyTrendsData(AssetTrendsEvent event) {
        Integer type = event.getType();
        log.info("记账类型={}",type);
        List<AssetTrendsVO> trendsVos = event.getTrendsVOList();
        List<NftAssetTrends> trendsList = BeanUtil.copyToList(trendsVos, NftAssetTrends.class);
        List<Long> prodIds = trendsList.stream().filter(s-> !s.getProdId().equals(0l)).map(NftAssetTrends::getProdId).distinct().collect(Collectors.toList());
        Map<Long, Product> productsMap;
        if(prodIds.size()>0){
            productsMap = productService.getMapByProdIds(prodIds);
        } else {
            productsMap = null;
        }
        trendsList.forEach(t->{
            if(!t.getProdId().equals(0l)){
                t.setChainCode(productsMap.get(t.getProdId()).getChain());
                t.setChannelCode(productsMap.get(t.getProdId()).getChannelCode());
            }else{
                Object o = RedisUtil.get("applyIssue::" + t.getDenomId());
                if(ObjectUtil.isNull(o)){
                    personNftService.getApplyIssueNftInfoByDenomId(t.getDenomId());
                    o = RedisUtil.get("applyIssue::" + t.getDenomId());
                }
                JSONObject obj = JSONObject.parseObject(o.toString());
                t.setChainCode(obj.getString("chainCode"));
                t.setChannelCode(obj.getString("channelCode"));
            }
        });

        if (type == TradeType.PLACE_ORDER.getValue() || type == TradeType.AIRDROP.getValue() || type == TradeType.SYNTHESIS_ADD.getValue() || type == TradeType.OPEN_HOLD.getValue() || type == TradeType.PLACE_ZERO.getValue() || type == TradeType.ROLL_IN_AIRDROP.getValue() || type == TradeType.PLACE_MAll_ORDER.getValue() ) {
//            Map<String, Map<String, String>> shopAddressMap = getShopAddressMap(trendsList);
//            Map<String, Blockchain> chainMap = getShopChainAddressMap(trendsList);

            Set<String> toUserIdSet = trendsList.stream().map(NftAssetTrends::getToUserId).collect(Collectors.toSet());
//            Map<String, ChannelUserRelationRecord> userChainMap = getUserChainMap(trendsList, toUserIdSet);
//            log.info("输出{}，{}",JSONObject.toJSONString(userChainMap),JSONObject.toJSONString(trendsList));
            for (NftAssetTrends trends : trendsList) {
                trends.setType(type);
                //from：店铺
//                trends.setFromAddress("dt".equals(trends.getChainCode())? (String) shopAddressMap.get(trends.getChannelCode()).get("address"):chainMap.get(trends.getChainCode()).getAddress());
//                trends.setFromMnemonic("dt".equals(trends.getChainCode())? (String) shopAddressMap.get(trends.getChannelCode()).get("mnemonic"):chainMap.get(trends.getChainCode()).getMnemonic());
                //to：用户
//                trends.setToAddress(userChainMap.get(trends.getToUserId() + UNDERLINE + trends.getChainCode()+ UNDERLINE + trends.getChannelCode()).getAddress());
//                trends.setToMnemonic(userChainMap.get(trends.getToUserId() + UNDERLINE + trends.getChainCode()+ UNDERLINE + trends.getChannelCode()).getMnemonic());
            }
        }
        if (type == TradeType.SYNTHESIS_DEL.getValue() || type == TradeType.EXCHANGE.getValue()) {
//            Map<String, Map<String, String>> shopAddressMap = getShopAddressMap(trendsList);
//            Map<String, Blockchain> chainMap = getShopChainAddressMap(trendsList);

            Set<String> foUserIdSet = trendsList.stream().map(NftAssetTrends::getFromUserId).collect(Collectors.toSet());
//            Map<String, ChannelUserRelationRecord> userChainMap = getUserChainMap(trendsList, foUserIdSet);
            for (NftAssetTrends trends : trendsList) {
                trends.setType(type);
                //from：用户
//                trends.setFromAddress(userChainMap.get(trends.getFromUserId() + UNDERLINE + trends.getChainCode() + UNDERLINE + trends.getChannelCode()).getAddress());
//                trends.setFromMnemonic(userChainMap.get(trends.getFromUserId() + UNDERLINE + trends.getChainCode() + UNDERLINE + trends.getChannelCode()).getMnemonic());
                //to：店铺
//                trends.setToAddress("dt".equals(trends.getChainCode())? (String) shopAddressMap.get(trends.getChannelCode()).get("address"):chainMap.get(trends.getChainCode()).getAddress());
//                trends.setToMnemonic("dt".equals(trends.getChainCode())? (String) shopAddressMap.get(trends.getChannelCode()).get("mnemonic"):chainMap.get(trends.getChainCode()).getMnemonic());
            }
        }
        if (type == TradeType.RE_GIFT.getValue() || type == TradeType.RED_ENVELOPE.getValue() || type == TradeType.SHARE_ENVELOPE.getValue()) {
            Set<String> userSet = trendsList.stream().map(NftAssetTrends::getFromUserId).collect(Collectors.toSet());
            Set<String> toUserSet = trendsList.stream().map(NftAssetTrends::getToUserId).collect(Collectors.toSet());
            userSet.addAll(toUserSet);
//            Map<String, ChannelUserRelationRecord> userChainMap = getUserChainMap(trendsList, userSet);
            for (NftAssetTrends trends : trendsList) {
                trends.setType(type);
                //from：用户
//                trends.setFromAddress(userChainMap.get(trends.getFromUserId() + UNDERLINE + trends.getChainCode() + UNDERLINE + trends.getChannelCode()).getAddress());
//                trends.setFromMnemonic(userChainMap.get(trends.getFromUserId() + UNDERLINE + trends.getChainCode() + UNDERLINE + trends.getChannelCode()).getMnemonic());
                //to：用户
//                trends.setToAddress(userChainMap.get(trends.getToUserId() + UNDERLINE + trends.getChainCode() + UNDERLINE + trends.getChannelCode()).getAddress());
//                trends.setToMnemonic(userChainMap.get(trends.getToUserId() + UNDERLINE + trends.getChainCode() + UNDERLINE + trends.getChannelCode()).getMnemonic());
            }
        }
        if (type == TradeType.CONSIGN.getValue() || type == TradeType.ROLL_OUT.getValue()) {
//            Map<String, Blockchain> chainMap = getChainMap(trendsList);
            Set<String> foUserSet = trendsList.stream().map(NftAssetTrends::getFromUserId).collect(Collectors.toSet());
//            Map<String, ChannelUserRelationRecord> userChainMap = getUserChainMap(trendsList, foUserSet);
            for (NftAssetTrends trends : trendsList) {
                trends.setType(type);
                //from：用户
//                trends.setFromAddress(userChainMap.get(trends.getFromUserId() + UNDERLINE + trends.getChainCode() + UNDERLINE + trends.getChannelCode()).getAddress());
//                trends.setFromMnemonic(userChainMap.get(trends.getFromUserId() + UNDERLINE + trends.getChainCode() + UNDERLINE + trends.getChannelCode()).getMnemonic());
                //to: 公户
                trends.setToUserId(null);
//                trends.setToAddress(chainMap.get(trends.getChainCode()).getMiddleAddress());
//                trends.setToMnemonic(chainMap.get(trends.getChainCode()).getMiddleMnemonic());
            }
        }
        if (type == TradeType.CONSIGN_BUY.getValue() || type == TradeType.CONSIGN_CANCEL.getValue() || type == TradeType.ROLL_IN.getValue()) {
//            Map<String, Blockchain> chainMap = getChainMap(trendsList);
            Set<String> toUserSet = trendsList.stream().map(NftAssetTrends::getToUserId).collect(Collectors.toSet());
//            Map<String, ChannelUserRelationRecord> userChainMap = getUserChainMap(trendsList, toUserSet);
            for (NftAssetTrends trends : trendsList) {
                trends.setType(type);
                //to：用户
//                trends.setToAddress(userChainMap.get(trends.getToUserId() + UNDERLINE + trends.getChainCode() + UNDERLINE + trends.getChannelCode()).getAddress());
//                trends.setToMnemonic(userChainMap.get(trends.getToUserId() + UNDERLINE + trends.getChainCode() + UNDERLINE + trends.getChannelCode()).getMnemonic());
                //from：公户
                trends.setFromUserId(null);
//                trends.setFromMnemonic(chainMap.get(trends.getChainCode()).getMiddleMnemonic());
//                trends.setFromAddress(chainMap.get(trends.getChainCode()).getMiddleAddress());
            }
        }
        insertBatch(trendsList);
    }

    private Map<String, Map<String, String>> getShopAddressMap(List<NftAssetTrends> trendsList) {
        Map<String, Map<String, String>> res = null;
        Set<String> channelCodes = trendsList.stream().filter(s->"dt".equals(s.getChainCode())).map(NftAssetTrends::getChannelCode).collect(Collectors.toSet());
        if(!channelCodes.isEmpty()){
            String url = shopConfig.getApiGatewayUrl() + "/nft/outapi/nftUser/getShopAddress?";
            List<String> usernames = channelCodes.stream().map(s -> "dtc-" + s).collect(Collectors.toList());
            Map<String, Object> params = new HashMap<>();
            params.put("userNames",usernames);
            String result = dataWebService.hutoolPostObjWebService(url, params);
            JSONObject jsonObject =  JSON.parseObject(result);
            if (null == jsonObject || jsonObject.getInteger("code") != Constant.SUCCESS_CODE ) {
                log.info("查询店铺地址失败,{}",jsonObject.getString("msg"));
                throw new YamiShopBindException( "查询店铺地址失败："+jsonObject.getString("msg"));
            }
            JSONArray data = jsonObject.getJSONArray("data");
            if(data.isEmpty()){
                throw new YamiShopBindException("查询店铺地址失败");
            }
            res = new HashMap<>();
            for (Object obj : data){
                Map<String, String> map = new HashMap<>();
                JSONObject jSONObject = (JSONObject) obj;
                map.put("channnelCode",jSONObject.getString("channnelCode"));
                map.put("address",jSONObject.getString("address"));
                map.put("mnemonic",jSONObject.getString("mnemonic"));
                res.put(jSONObject.getString("channnelCode"),map);
            }
        }
        return res;
    }
    private Map<String, Blockchain> getShopChainAddressMap(List<NftAssetTrends> trendsList) {
        Map<String, Blockchain> chainMap = null;
        Set<String> chainCodes = trendsList.stream().filter(s->!"dt".equals(s.getChainCode())).map(NftAssetTrends::getChainCode).collect(Collectors.toSet());
        if(!chainCodes.isEmpty()){
            List<Blockchain> list = blockchainService.list(new LambdaQueryWrapper<Blockchain>().eq(Blockchain::getStatus, 1).in(Blockchain::getCode, chainCodes));
            chainMap = list.stream().collect(Collectors.toMap(Blockchain::getCode, Function.identity()));
        }
        return chainMap;
    }

    private Map<String, Blockchain> getChainMap(List<NftAssetTrends> trendsList) {
        Set<String> chainCodes = trendsList.stream().map(NftAssetTrends::getChainCode).collect(Collectors.toSet());
        List<Blockchain> list = blockchainService.list(new LambdaQueryWrapper<Blockchain>().eq(Blockchain::getStatus, 1).in(Blockchain::getCode, chainCodes));
        Map<String, Blockchain> chainMap = list.stream().collect(Collectors.toMap(Blockchain::getCode, Function.identity()));
        return chainMap;
    }

    private Map<String, ChannelUserRelationRecord> getUserChainMap(List<NftAssetTrends> trendsList, Set<String> userSet) {
        Set<String> chainCodes = trendsList.stream().map(NftAssetTrends::getChainCode).collect(Collectors.toSet());
        Set<String> channelCode = trendsList.stream().map(NftAssetTrends::getChannelCode).collect(Collectors.toSet());
        List<ChannelUserRelationRecord> userList = chainUserService.list(Wrappers.lambdaQuery(ChannelUserRelationRecord.class)
                .eq(ChannelUserRelationRecord::getType, "1")
                .in(ChannelUserRelationRecord::getChannelCode, channelCode)
                .in(ChannelUserRelationRecord::getChainCode, chainCodes)
                .in(ChannelUserRelationRecord::getUserId, userSet));
        Map<String, ChannelUserRelationRecord> userChainMap = userList.stream().collect(Collectors.toMap(c -> c.getUserId() + UNDERLINE + c.getChainCode() + UNDERLINE + c.getChannelCode(), Function.identity()));
        return userChainMap;
    }


    private void saveTrends(List<NftAssetTrends> trendsList) {
        List<NftAssetSettle> updateSettles = new ArrayList<>();
        Date date = new Date();
        for (NftAssetTrends trends : trendsList) {
            trends.setStatus(0);
            trends.setCreateTime(date);
            NftAssetTrends one = getOne(new LambdaQueryWrapper<NftAssetTrends>()
                    .eq(NftAssetTrends::getTokenId, trends.getTokenId()).ne(NftAssetTrends::getStatus, 3).orderByDesc(NftAssetTrends::getCreateTime).orderByDesc(NftAssetTrends::getId).last("limit 1"));
            if (Objects.nonNull(one) && !one.getToAddress().equals(trends.getFromAddress())) {
                log.error("异常交易流向：" + JSON.toJSONString(trends));
                String eMsg = "资产来源异常";
                List<String> frozenUserIds = personNftService.frozenByTokenId(trends.getTokenId());
                if (CollUtil.isNotEmpty(frozenUserIds)) {
                    eMsg += "，已冻结" + frozenUserIds.toString() + "用户持仓";
                }
                List<String> lockingUserIds = consignService.lockingConsignByTokenId(trends.getTokenId());
                if (CollUtil.isNotEmpty(lockingUserIds)) {
                    eMsg += "，已锁定" + lockingUserIds.toString() + "用户寄售";
                }
                trends.setStatus(3);
                trends.setErrorMsg(eMsg);
                continue;
            }
            NftAssetSettle settle = assetSettleMapper.selectOne(new LambdaQueryWrapper<NftAssetSettle>().eq(NftAssetSettle::getTokenId, trends.getTokenId()).eq(NftAssetSettle::getStatus, 0).orderByDesc(NftAssetSettle::getSettleTime).last("limit 1"));
            if (Objects.isNull(settle) || DateUtil.compare(date, settle.getSettleTime()) >= 0) {
                //新增周期
                String cycle = sysConfigService.getValue("NFT_ASSET_DELIVERY_CYCLE");
                int cycleNum = StringUtils.isNotEmpty(cycle) ? Integer.parseInt(cycle) : 80;
                DateTime settleTime = DateUtil.beginOfDay(DateUtil.offsetDay(date, cycleNum));
                NftAssetSettle newSettle = new NftAssetSettle();
                newSettle.setDenomId(trends.getDenomId());
                newSettle.setTokenId(trends.getTokenId());
                newSettle.setFromMnemonic(trends.getFromMnemonic());
                newSettle.setFromAddress(trends.getFromAddress());
                newSettle.setToUserId(trends.getToUserId());
                newSettle.setToAddress(trends.getToAddress());
                newSettle.setStatus(0);
                newSettle.setProdId(trends.getProdId());
                newSettle.setChainCode(trends.getChainCode());
                newSettle.setSettleTime(settleTime);
                newSettle.setCreateTime(date);
                assetSettleMapper.insert(newSettle);
                trends.setSettleId(newSettle.getId());
            } else {
                //更新资产接收方
                settle.setToUserId(trends.getToUserId());
                settle.setToAddress(trends.getToAddress());
                settle.setUpdateTime(date);
                updateSettles.add(settle);
                trends.setSettleId(settle.getId());
            }
        }
        if (CollUtil.isNotEmpty(updateSettles)) {
            assetSettleService.updateBatchById(updateSettles);
        }
        saveBatch(trendsList);
    }


    @Override
    public void checkTrends() {
        Boolean value = RedisUtil.hasKey(CHECK_TRENDS_KEY);
        if (value) {
            log.info("=====正在对账中，忽略此次=====" );
            return;
        }
        int num = 0;
        while (true) {
            List<NftAssetTrends> list = assetTrendsMapper.getTrendsExcludeException(QUERY_PAGE_SIZE);
            if (CollUtil.isEmpty(list)) {
                RedisUtil.del(CHECK_TRENDS_KEY);
                break;
            }
            log.info("list: {}, 详情:{}", list.size(), list.toString());
            num++;
            RedisUtil.set(CHECK_TRENDS_KEY, num, 0);
            Map<String, List<NftAssetTrends>> tokenIdMap = list.stream().collect(Collectors.groupingBy(NftAssetTrends::getTokenId));
            wai: for (String tokenId : tokenIdMap.keySet()) {
                List<NftAssetTrends> trendsList = tokenIdMap.get(tokenId);
                for (NftAssetTrends trends : trendsList) {
                    try {
                        TradeType type = TradeType.instance(trends.getType());
                        String outId = trends.getOutId();
                        switch (type) {
                            case PLACE_ORDER:
                                //查订单
//                                ServerResponseEntity<Void> orderRes = orderFeignClient.checkOrderReconciliation(outId);
//                                if (BooleanUtil.isFalse(orderRes.isSuccess()))throw new YamiShopBindException(orderRes.getMsg());
                                break;
                            case AIRDROP:
                                //空投
//                                ServerResponseEntity<Void> airRes = personnftClient.checkAirdropReconciliation(outId);
//                                if (BooleanUtil.isFalse(airRes.isSuccess()))throw new YamiShopBindException(airRes.getMsg());
                                break;
                            case RE_GIFT:
                                //转赠
//                                ServerResponseEntity<Void> incrRes = personnftClient.checkIncrementReceiving(outId);
//                                if (BooleanUtil.isFalse(incrRes.isSuccess()))throw new YamiShopBindException(incrRes.getMsg());
                                break;
                            case SYNTHESIS_DEL:
                                break;
                            case SYNTHESIS_ADD:
                                break;
                            case CONSIGN:
//                                ServerResponseEntity<Void> consign = consignFeignClient.checkConsign(outId);
//                                if (!consign.isSuccess()) throw new YamiShopBindException(consign.getMsg());
                                break;
                            case CONSIGN_CANCEL:
//                                ServerResponseEntity<Void> cancel = consignFeignClient.checkConsignCancel(outId);
//                                if (!cancel.isSuccess()) throw new YamiShopBindException(cancel.getMsg());
                                break;
                            case CONSIGN_BUY:
//                                ServerResponseEntity<Void> consignBuy = consignFeignClient.checkConsignBuy(outId);
//                                if (!consignBuy.isSuccess()) throw new YamiShopBindException(consignBuy.getMsg());
                                break;
                            case ROLL_OUT:
                                break;
                            case ROLL_IN:
                                break;
                            case EXCHANGE:
                                break;
                            case OPEN_HOLD:
                                break;
                            case RED_ENVELOPE:
                                break;
                            case PLACE_ZERO:
                                break;
                            case ROLL_IN_AIRDROP:
                                break;
                            case PLACE_MAll_ORDER:
                                break;
                            case SHARE_ENVELOPE:
                                break;
                            default:
                                throw new YamiShopBindException("类型错误: " + trends.getType() + "-" + type.getInfo());
                        }
                        trends.setStatus(1);
                        trends.setUpdateTime(new Date());
                        updateById(trends);
                    } catch (Exception e) {
                        log.error(trends.getId() + "对账异常：", e);
                        trends.setStatus(2);
                        trends.setUpdateTime(new Date());
                        updateById(trends);
                        saveTrendsException(trends, e);
                        continue wai;
                    }
                }
            }
        }

    }

    private void saveTrendsException(NftAssetTrends trends, Exception e) {
        AssetException exception = new AssetException();
        exception.setTokenId(trends.getTokenId());
        exception.setType(1);
        exception.setAssetId(trends.getId());
        exception.setErrorMsg(e.getMessage().length() > 10000 ? e.getMessage().substring(0, 10000) : e.getMessage());
        exception.setCreateTime(new Date());
        assetExceptionMapper.insert(exception);
    }


}
